Skip to content

feat: Make Sonnet 4.6 free for one week in review mode#295

Open
kiloconnect[bot] wants to merge 10 commits intomainfrom
feat/sonnet-46-free-review-promo
Open

feat: Make Sonnet 4.6 free for one week in review mode#295
kiloconnect[bot] wants to merge 10 commits intomainfrom
feat/sonnet-46-free-review-promo

Conversation

@kiloconnect
Copy link
Contributor

@kiloconnect kiloconnect bot commented Feb 17, 2026

Summary

Make Claude Sonnet 4.6 free for one week in Code Reviewer (review mode).

Promotion window: 2026-02-18 11:00 UTC → 2026-02-25 11:00 UTC (7 days)

Changes

Free model promotion (following PR #27 patterns)

  • Add sonnet_46_free_review_model in src/lib/providers/anthropic.ts with review_only: true flag
  • Extend KiloFreeModel type with review_only, promotion_start, and promotion_end fields
  • Register the model in kiloFreeModels array in src/lib/models.ts
  • Hide review-only models from the public model list (same pattern as slackbot_only)
  • Block access to review-only models outside of internal API (Code Reviewer) and outside the promotion window

Promotion-aware model selection

  • Add getDefaultCodeReviewModel() function that returns the promotional model during the active window
  • Update prepare-review-payload.ts to use the promotion-aware model selection
  • When no custom model is configured, code reviews automatically use Sonnet 4.6 during the promotion

Tracking & Reporting

  • Structured logging: Log when the promotional model is selected for a code review (includes reviewId, owner, promotion dates)
  • Admin endpoint: New getReviewPromotionStats tRPC query in the admin code reviews router that reports:
    • Total/completed/failed reviews using the promo model
    • Unique users and organizations
    • Daily breakdown with per-day user counts
    • Promotion active status

Access control

  • Review-only models return "model does not exist" when accessed outside of Code Reviewer (internalApiUse)
  • Promotion automatically expires — after promotion_end, the model returns "does not exist"
  • No changes needed to billing: code reviews already use skipBalanceCheck: true

Testing

curl -s "http://localhost:3000/api/trpc/admin.codeReviews.getReviewPromotionStats" -H "Cookie: ..." | jq

{
  "result": {
    "data": {
      "promotionModelId": "anthropic/claude-sonnet-4.6:review",
      "promotionInternalModelId": "anthropic/claude-sonnet-4.6",
      "promotionStart": "2026-02-18T00:00:00Z",
      "promotionEnd": "2026-02-25T00:00:00Z",
      "isActive": true,
      "totalPromoReviews": 0,
      "completedPromoReviews": 0,
      "failedPromoReviews": 0,
      "uniqueUsers": 0,
      "uniqueOrgs": 0,
      "dailyBreakdown": []
    }
  }
}

Built for alex.alecu by Kilo for Slack

- Add claude-sonnet-4-6-20250514 as a free promotional model for Code Reviewer
- Promotion window: 2026-02-18 to 2026-02-25 (7 days)
- Model is hidden from public model list (review_only flag)
- Only accessible through internal API (Code Reviewer) during promotion
- Add promotion tracking admin endpoint (getReviewPromotionStats)
- Add structured logging for promotion model usage
*/
export function getDefaultCodeReviewModel(): string {
const promoModel = getActiveReviewPromotionModel();
return promoModel?.internal_id ?? BASE_CODE_REVIEW_MODEL;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: getDefaultCodeReviewModel() returns internal_id instead of public_id, which means the promotional model will not be recognized as free by the API route.

The API route at src/app/api/openrouter/[...path]/route.ts uses the model ID to check isFreeModel(), isReviewOnlyModel(), and isRateLimitedModel() — all of which match against public_id. The applyProviderSpecificLogic() function in src/lib/providers/index.ts:243 then rewrites requestToMutate.model from public_idinternal_id before sending to OpenRouter.

By returning internal_id here, the model bypasses the review-only access gate and isn't recognized as free, so users will be charged credits for what's supposed to be a free promotional model.

For reference, the Slack integration correctly uses public_id (opus_46_free_slackbot_model.public_id) in src/lib/integrations/slack-service.ts:20.

Suggested change
return promoModel?.internal_id ?? BASE_CODE_REVIEW_MODEL;
return promoModel?.public_id ?? BASE_CODE_REVIEW_MODEL;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is valid, isn't it?

@kiloconnect
Copy link
Contributor Author

kiloconnect bot commented Feb 17, 2026

Code Review Summary

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
src/lib/code-reviews/core/constants.ts 25 getDefaultCodeReviewModel() returns internal_id (anthropic/claude-sonnet-4.6) instead of public_id (anthropic/claude-sonnet-4.6:review). The code review system sends this model through the OpenRouter proxy, which gates review models via allowed_uses check against public_id. Using internal_id means the proxy won't recognize it as the review promo model and will route it as a regular paid Sonnet 4.6 request.

WARNING

File Line Issue
src/routers/admin-code-reviews-router.ts 400 The query matches cliSessions.last_model against promoModelId which is internal_id (anthropic/claude-sonnet-4.6). If last_model stores the public_id (anthropic/claude-sonnet-4.6:review), this query will return zero results. Conversely, if it stores the internal_id, it will also match non-promotional Sonnet 4.6 usage, inflating stats.

SUGGESTION

File Line Issue
src/lib/providers/anthropic.ts N/A Unnecessary as casts — satisfies KiloFreeModel is already used for the new models but as KiloFreeModel[] remains on the array in src/lib/models.ts line 73.
src/lib/models.ts N/A isFreeModelAllowedForUse is exported but never used (if it exists).
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
src/lib/providers/openrouter/sync-providers.ts 120-121 The review model (sonnet_46_free_review_model) has is_enabled: true and inference_providers: ['anthropic'], so it passes the model.is_enabled && model.inference_providers.length > 0 filter and will be synced to the provider database. This is likely harmless since enhancedModelList already excludes it from the public model list, but worth verifying this is intentional.
src/lib/models.ts 73 The as KiloFreeModel[] cast on the kiloFreeModels array could be replaced with satisfies KiloFreeModel[] for consistency with the satisfies pattern adopted in anthropic.ts.
Files Reviewed (9 files)
  • src/app/api/openrouter/[...path]/route.ts - 0 new issues
  • src/components/cloud-agent-next/hooks/useResumeConfigModal.ts - 0 new issues
  • src/components/cloud-agent/hooks/useResumeConfigModal.ts - 0 new issues
  • src/lib/code-reviews/core/constants.ts - 1 issue (existing)
  • src/lib/code-reviews/triggers/prepare-review-payload.ts - 0 new issues
  • src/lib/models.ts - 1 issue (existing)
  • src/lib/providers/anthropic.ts - 1 issue (existing)
  • src/lib/providers/kilo-free-model.ts - 0 new issues
  • src/lib/providers/openrouter/index.ts - 0 new issues
  • src/routers/admin-code-reviews-router.ts - 1 issue (existing)

Fix these issues in Kilo Cloud

Use leftJoin instead of innerJoin for cli_sessions to avoid dropping
reviews without a session. Fix COALESCE type mismatch, default empty
stats result, exclude review_only models from rate limiting, and remove
deprecated DEFAULT_CODE_REVIEW_MODEL constant.
@alex-alecu alex-alecu requested review from RSO and markijbema February 18, 2026 10:16
display_name: 'Anthropic: Claude Sonnet 4.6 (Free for Code Reviewer)',
description: 'Claude Sonnet 4.6 — free for one week in Code Reviewer (review mode)',
context_length: 1_000_000,
max_completion_tokens: 16384,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_completion_tokens: 16384,
max_completion_tokens: 128000,

export const sonnet_46_free_review_model = {
public_id: SONNET_46_REVIEW_PROMO_MODEL_ID,
display_name: 'Anthropic: Claude Sonnet 4.6 (Free for Code Reviewer)',
description: 'Claude Sonnet 4.6 — free for one week in Code Reviewer (review mode)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does (review mode) add to this description? i don't think users can actually use this model for review model locally, right?

Comment on lines +38 to +39
promotion_start: '2026-02-18T11:00:00Z', // 6 AM East Coast
promotion_end: '2026-02-25T11:00:00Z',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding these two new fields, I'd just use: is_enabled: current_time < promotion_end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That way you also don't need to have a isReviewPromotionActive because then the promotion is active as long as model.is_enabled is true

}),

// Sonnet 4.6 free review promotion tracking
getReviewPromotionStats: adminProcedure.query(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just an orphaned procedure that you plan to use later?

flags: ['reasoning', 'prompt_cache', 'vision'],
gateway: 'openrouter',
internal_id: 'anthropic/claude-sonnet-4.6',
inference_providers: ['anthropic'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inference_providers: ['anthropic'],
inference_providers: [],

for openrouter models this is only necessary if you want to constrain which provider is used.

.concat(kiloFreeModels.filter(m => m.is_enabled).map(model => convertFromKiloModel(model)))
.concat(
kiloFreeModels
.filter(m => m.is_enabled && !m.allowed_uses?.length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this change I don't think it will be possible for users to switch back to the free promotional model after switching to another model, unless this PR is also finished: #149

*/
export const DEFAULT_CODE_REVIEW_MODEL = 'anthropic/claude-sonnet-4.5';
export function getDefaultCodeReviewModel(): string {
const promoModel = getActiveReviewPromotionModel();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the promo ends, what is the expecte experience for existing users? will the get an error or will they silently be moved to the paid model? if the latter, then you don't actually need to add a custom model slug and it would suffice to override the cost administration

* If a review-only promotional model is currently active, it takes precedence.
*/
export const DEFAULT_CODE_REVIEW_MODEL = 'anthropic/claude-sonnet-4.5';
export function getDefaultCodeReviewModel(): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this belongs here, and we want to restrict this file to only the constants

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this file was only ever meant for ENV vars which dont need to be ENV vars)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants